Bootstrapping Zend Framework via ErrorDocument 404
Traditionally Zend Framework applications are bootstrapped using mod_rewrite as recommended in the manual and various tutorials. For non-Apache servers such nginx, similar methods are provided. But it's also possible to use Apache's ErrorDocument configuration to kick off a Zend Framework based application. This comes in handy if the web application requires many Apache aliases and other legacy configurations that make it tedious or performance inefficient to use mod_rewrite. In such a case Apache is configured to just "serve" per its configuration and anything that results in a 404 because a file wasn't found will be redirected to the Zend Framework bootstrap file.
In order to get that working all you need in your httpd.conf or .htaccess is:
ErrorDocument 404 /index.php
Now, when you try to hit a URL such as 'http://example.com/hello/world', the file is not found, and Apache's error handler will kick in, and call index.php, which should be the ZF bootstrap file. And as long as you have a HelloController with worldAction() method, it should be invoked as you'd expect. There are, however, a number of pitfalls that need to be taken into consideration otherwise you'll soon run into strange issues.
The Modified Apache Context
Right off the bat, the first problem is that the query string is no longer accessible the regular way. $_GET is empty. So if you call http://example.com/hello/world?foo=bar and you expect to be able to grab that with $this->_getParam('foo') you'll be disappointed, because it's not there. $_GET is blank, and therefore, the Zend_Controller_Request_Abstract class logic to populate the parameters and query string interaction is broken. This is corrected by plugging Zend_Controller_Request_Apache404 into the Front Controller which is supposed to provide the compatibility with Apache 404s.
Somewhere in your bootstrap mechanism you'll want to have:
Zend_Controller_Front::getInstance()->setRequest(new Zend_Controller_Request_Apache404());
This will populate $_GET with data from the REQUEST_QUERY_STRING server environment variable, which Apache sets up. Well, it should anyway. Unfortunately as of version 1.10.4 the Zend_Controller_Request_Apache404 class has a bug in it, and it tries to access REQUEST_QUERYSTRING instead. This has been reported as a bug. As a workaround, you can either modify the class, copy and paste it into a new class, or copy $_SERVER['REQUEST_QUERY_STRING'] into $_SERVER['REQUEST_QUERYSTRING'].
GET requests aren't the only issue. POST variables are also not accessible (with Apache 2.0+). And unfortunately Apache provides no alternate environment variable that contains the POST context. Makes sense, since POSTs may be multi-part bodies and can be a bit more complex. In this case, mod_actions saves the day by providing a way of calling a particularly script depending on the type of HTTP action. In this case, we'll route all POST actions to index.php.
Script POST /index.php
The pitfall here is that you can no longer submit a POST to anything other than the Zend Framework bootstrap file. So if you were planning on running legacy PHP side by side with your ZF application, you'll have to resort to additional Apache configuration in order to make that fly.
Not as immediately apparent is the issue of having all your pages served with status code 404. Remember, the app is bootstrapped using Apache's 404 error handler. The Zend Framework currently doesn't set any headers by default as it assumes the default response code to be 200. So because PHP doesn't set anything, Apache will use its own 404 status code with your regular application body as content, so it'll probably still show up in most browsers. However, automated tools and search engines will freak out and probably not index your site at all. This has also been reported as a bug.
Considering the various issues that I've encountered it's almost safe to assume that nobody else has really tried bootstrapping a Zend Framework web application using the Apache404 handler. Some of these problems would have probably surfaced and been remedied by now.
Print This Post
MySQL Replication for Offsite Backups
I'm cheap. I tend to run pet projects on shoe string budgets. For one, it's just a good habit. It's easier to increase spending when it's really necessary (after some growth and revenue) than it is to turn off a service that you've come to rely on.
Whatever the case may be, having backups is crucial. Most businesses do not recover from a failure that includes data loss if they don't have backups. I have a production server that is running out there somewhere and if it were to go down, I need to be certain that I don't lose my data. It could go down for any reason at any time. Network issues, hardware failure, ISP going out of business, etc. So I want to be sure that I have all my important data where I need it, backed up at home on media under my direct control.
Although MySQL on my production servers is typically configured to only listen on localhost (127.0.0.1), I use SSH port forwarding to access it remotely.
The command for that is rather easy. On my laptop (runs Linux) I use the following command:
$ ssh user@example.com -L 3300:localhost:3306
Aside from using SSH to log into my server it also means that any connection made to port 3300 on my laptop is forwarded to the server, and on that side it'll connect to localhost port 3306, which is where MySQL is listening. That way I can fire up the MySQL Query Browser on my laptop connect to localhost port 3300 and work on my server's MySQL instance. Everything else is transparent, not to mention encrypted.
So with that in mind, I configure my production machine's MySQL server for replication (turn on binlog). And on the slave side (A headless Linux machine I have sitting under my desk at home) I setup key based authentication and use the following command:
$ while [ 1 ]; do ssh user@example.com -L 3300:localhost:3306 -N; echo "reconnecting..."; done
This will keep reconnecting in case of a connection failure. The MySQL slave will keep retrying to get to the master and I don't really have to worry about much.
Print This Post
Zend Framework: Coding by Convention - Part 2
A number of components sometimes accept strings or arrays as parameter. Developers coming from statically typed languages might cringe a bit, but this is really just about taking advantage of the flexibility of the language. With flexibility comes freedom and complexity, so having an established convention helps quite a bit.
One component that makes extensive use of accepting both strings and arrays is Zend_Db_Select. When building queries, it allows you to specify parameter either way:
// SELECT username FROM users WHERE id = 1
$select->from('users', 'username')->where('id = ?', 1);
// Same query again, with array syntax
$select->from(array('users'), array('username'))->where('id = ?', 1);
This can make for much more readable queries when things become more complex:
class Friendships extends Zend_Db_Table_Abstract
{
/**
* Select users suggested as friends
*
* Algorithm picks out your friends' friends that you don't already
* have added to your list, but which at least $threshold friends have in common.
*
* @param string $useruuid Avatar's UUID
* @param int $threshold How many friends must have the person in common
* @return Zend_Db_Table_Rowset_Abstract
*/
public function getSuggested($useruuid, $threshold = 4,
$page = null, $itemsPerPage = null)
{
$select = $this->select()
->setIntegrityCheck(false)
// friends
->from(array('f' => $this->_name),
array()) // no columns
->where('f.useruuid = ? AND f.dateaccepted IS NOT NULL', $useruuid)
// friends' friends that aren't me
->join(array('ff' => $this->_name),
'f.frienduuid = ff.useruuid AND ' .
'ff.frienduuid != f.useruuid AND ' .
'ff.dateaccepted IS NOT NULL',
array()) // no columns
->join(array('u' => 'users'),
'ff.frienduuid = u.uuid',
array('*'))
// my friends again (left joined)
->joinLeft(array('mf' => $this->_name),
'ff.frienduuid = mf.frienduuid AND ' .
'mf.useruuid = f.useruuid AND ' .
'mf.dateaccepted IS NOT NULL',
array()) // no columns
// filter my friends' friends that aren't my friends
->where('mf.frienduuid IS NULL')
->group('ff.frienduuid') // group by suggested friends
->having('COUNT(f.frienduuid) >= ?', $threshold)
->order('COUNT(f.frienduuid) DESC');
if ($page !== null or $itemsPerPage !== null) {
$select->limitPage($page, $itemsPerPage);
}
return $this->fetchAll($select);
}
}
Print This Post
Zend Framework: Coding by Convention
This is really something I've been wanting to point out because, for one, I very much like and agree with the approach, and second, it's something that any developer using the Zend Framework should digest and take into consideration when writing their own code.
There are numerous components that will accept configuration options, and usually that method is called setOptions($array) and it accepts an associative array (key/value pairs) as parameter.
And often, setOptions() iterates over the array and calls setOption($key, $value) as demonstrated by the following example:
public function setOptions($options)
{
foreach ($options as $option => $value) {
$this->setOption($option, $value);
}
return $this;
}
And typically, setOption() takes the first parameter (the key) and checks whether there's a method that matches "set" followed by the key name. For example, setOption('active', true) will end up calling setActive(true). The code often looks similar to the following:
public function setOption($option, $value)
{
$method = 'set' . $option;
if (method_exists($this, $method)) {
$this->$method($value);
} else {
throw new Exception('Unknown option: ' . $option);
}
return $this;
}
This comes in handy when you're trying to configure a component that accepts a plethora of options, which you can then conveniently store in an array.
Furthermore, the constructor of a class could accept $options as first parameter (as is often the case), and also check whether it is an instance of Zend_Config, in which case it first converts it using $options = $options->toArray();
public function __construct($options = null)
{
if ($options instanceof Zend_Config) {
$options = $options->toArray();
}
if (is_array($options)) {
$this->setOptions($options);
}
}
Another convention you may have noticed is that the setter methods tend to "return $this;". This is known as fluent interface and allows for more concise, readable code by being able to chain method calls.
Print This Post
Implementing short URLs using case-sensitive Routes with Zend Framework
Since short URLs are all the rage these days, I wanted to outfit one of my websites with its own short URL capability.
A simple way to accomplish this is to base62 encode a numeric identifier (the database table's primary key). In order to identify a URL as a short URL, it will be prefixed with an uppercase 'S'. So, a short URL would be something like this: http://mixoom.com/Snfup8.
With regular expressions it's simple to turn off and on case sensitivity using (?-i) and (?i) respectively. So ultimately the new route needed in order to accomplish mapping the short URL is the following:
$route = new Portal_Controller_Router_Route_Regex(
'(?-i)S([\w\d]+)',
array('controller' => 'photos',
'action' => 'shorturl'),
array('shortid' => 1)
);
Print This Post