Tag: CakePHP

Setting up Nginx and CakePHP 2.0

Nginx is a pretty awesome web server (fast, and easy to configure… at least I prefer the syntax over some other popular web servers).

I figured to share the installation process of both CakePHP 2.0 and Nginx on Ubuntu 11.04 (Natty).

Let’s fire up the terminal…

(I presume you have some basic knowledge of *nix so I won’t go into details about the commands, etc.)

sudo apt-get install git

Next, let’s get a fresh version of cake 2:

cd /cake
sudo git clone https://github.com/cakephp/cakephp.git
sudo git checkout 2.0
sudo git pull

Git should tell us that we are “up-to-date”.

Alright, now we have to setup our environment.
The best way I found of going about it (after trying quite a few methods out there) is by the running the excellent set of scripts, which you can find here.
Pull the scripts from the git repo, in the similar way as shown above, into some reasonable local destination. Once you have the files locally, simply run:

sudo ./install.sh

After answering a few questions… our LEMP environment is ready!

Let’s see if Nginx is working as expected…
In the browser head over to localhost, at least at the time of this writing, you get a phpinfo() page served up by default.

So we are satisfied that Nginx is serving up PHP and now it’s time to setup a CakePHP 2.0 app.
When we’ve pulled CakePHP in the very beginning, it came with a skeleton app, which we will use for growing our new one.

Let’s copy it someplace easily accessible (assuming we are in the root of cake… you should see “app”, “vendors”, “lib” directories):

sudo cp -r app /web/

In order not to move cake anywhere, we’ll create a symbolic link to our lib.
(Presuming we are now in the “/web” directory):

sudo ln -s /cake/cakephp/lib lib

So at this point we have php, web server, cake core, skeleton app, mysql all ready to go.
The only remaining part is to tell the web server about our cake app. Similarly to Apache we can setup virtual hosts and rewrite rules in Nginx. I don’t know many details about setting up Nginx and all the rewrite rule tricks available, but as mentioned before, from the examples the syntax looks quite simple and one should be able to decipher the directives relatively easily.

… Being lazy and not wanting to go through the docs, I googled around and thanks to this post it was quite fast to setup a virtual host for the app.

After the installation Nginx will have a setting file in:
/etc/nginx/sites-available/default
(This particular set of installation steps is applicable to Ubuntu, but hopefully you’ll know how to achieve the same procedure in your own OS).

If we review the file quickly it seems like a solid starting point, but we need to have some rewrite rules for cake to make pretty-urls work.
Well, once again thanks to aforementioned post, all we have to do is add the snippet below to our default config (hey, at least it’s working for me):

# rewrite rules for cakephp
  location / {
    root   /web/app/webroot;
    index  index.php index.html;
    try_files $uri $uri/ index.php;

    # If the file exists as a static file serve it
    # directly without running all
    # the other rewite tests on it
    if (-f $request_filename) {
      break;
    }
    if (!-f $request_filename) {
      rewrite ^/(.+)$ /index.php?url=$1 last;
      break;
    }
 }

The root setting is pointing to our app’s webroot, of course… which in trun becomes the root of the virtual host (let’s just use localhost for now, otherwise you’d need to take a few additional steps, but that’s beyond the scope of this post).
Hopefully the code of the setting is not too hard to figure out.

So at this point we have a virtual host pointing to our app and all the settings in place, let’s restart the web server:

sudo /etc/init.d/nginx restart

… and if all goes well, once you visit localhost in your browser, you should see the CakePHP 2.0 welcome page.

Quick comparison of Nginx and Apache

This was a quick test as I was playing around with Nginx and CakePHP 2.0.

The numbers were interesting, however.
What I did:
- Setup a virtual box with Windows host
- OS: Ubuntu (Natty)
- PHP 5.3.8
- CakePHP 2.0-beta (freshly pulled)
- apache2 (2.2.17)
- nginx (1.0.5)

Nothing was tweaked or tuned. I’ve setup both servers to use virtual hosts and simply load the default CakePHP page (i.e. fresh install).
There is no app behind any of this, but we are touching pieces of the framework and some PHP logic.
(Comparison is about the web servers anyway)…

Anyway, start apache and run:

ab -kc 10 -t 30 http://localhost/

So we’ll use apache benchmark to beat the localhost a little (for 30 seconds) and get some numbers:

Benchmarking localhost (be patient)
Finished 839 requests

Server Software:        Apache/2.2.17
Server Hostname:        localhost
Server Port:            80

Document Path:          /
Document Length:        4481 bytes

Concurrency Level:      10
Time taken for tests:   30.009 seconds
Complete requests:      839
Failed requests:        0
Write errors:           0
Keep-Alive requests:    839
Total transferred:      4109432 bytes
HTML transferred:       3759559 bytes
Requests per second:    27.96 [#/sec] (mean)
Time per request:       357.679 [ms] (mean)
Time per request:       35.768 [ms] (mean, across all concurrent requests)
Transfer rate:          133.73 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.7      0       7
Processing:   203  355 121.7    352    2931
Waiting:      202  355 121.7    352    2931
Total:        203  355 122.0    352    2938

Percentage of the requests served within a certain time (ms)
  50%    352
  66%    360
  75%    365
  80%    368
  90%    377
  95%    385
  98%    393
  99%    404
 100%   2938 (longest request)

Now, shutdown apache, start nginx and repeat the above test:

Benchmarking localhost (be patient)
Finished 4451 requests

Server Software:        nginx/1.0.5
Server Hostname:        localhost
Server Port:            80

Document Path:          /
Document Length:        4481 bytes

Concurrency Level:      10
Time taken for tests:   30.001 seconds
Complete requests:      4451
Failed requests:        0
Write errors:           0
Keep-Alive requests:    0
Total transferred:      21367368 bytes
HTML transferred:       19972014 bytes
Requests per second:    148.36 [#/sec] (mean)
Time per request:       67.403 [ms] (mean)
Time per request:       6.740 [ms] (mean, across all concurrent requests)
Transfer rate:          695.53 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.3      0       7
Processing:    18   67  11.6     67     204
Waiting:        5   40  22.1     42     198
Total:         18   67  11.6     67     204

Percentage of the requests served within a certain time (ms)
  50%     67
  66%     71
  75%     73
  80%     75
  90%     80
  95%     85
  98%     93
  99%    102
 100%    204 (longest request)

OK, let’s see:
Total requests served: Apache – 839, Nginx – 4451
Requests per second: Apache – 27.96, Nginx – 148.36

The other numbers are quite unbelievable as well.

What’s the point of all this? Tutorial on CakePHP 2.0 + Nginx is coming soon here ;)

Offload read queries to a replica DB for better performance

In most web application, which require a lot of find()'s especially if more than a couple of models are involved, you should probably consider offloading those operations to read-only replica of your DB. This is typically achieved by having a master/slave or master/master configuration. In high traffic application you might have a cluster of databases, but for the purpose of this example we’ll only use two data sources: “default” and “replica”.

Therefore our basic database.php will look something like this:

public $default = array(
		'driver' => 'mysql',
		'persistent' => false,
		'host' => 'production.example.com',
		'login' => 'user',
		'password' => 'password',
		'database' => 'production',
		'prefix' => '',
		'encoding' => 'utf8'
	);

	public $replica = array(
		'driver' => 'mysql',
		'persistent' => false,
		'host' => 'readonly.example.com',
		'login' => 'user',
		'password' => 'password',
		'database' => 'readonly',
		'prefix' => '',
		'encoding' => 'utf8'
	);

This prepares our application to use two data sources, as needed.

Next, let’s imagine we have a good ol’ blog and need to grab various information to build a list of posts.
In our Posts Controller, we’ll have some method that gets the required information:

$this->Post->getListofPosts();

The method above will have to involve additional models to get all of the needed info (Author, Tag, (PostsTag for the join table), PostRating… and maybe a few other models). The point is that this is enough operations already to consider offloading them to our read-only DB server.

The actual process is quite simple.
First, we’ll create a generic method in our App Model:

protected function _switchDataSource($models, $datasource = 'default') {
		if (is_array($models)) {
			foreach ($models as $model) {
				ClassRegistry::init($model)->setDataSource($datasource);
			}
		}
	}

I hope this code is simple enough, but the implementation example is coming up…
It’s worth to note that ClassRegistry::init() will cache your model information (object instance, to be more precise) in memory, therefore in a more complex case (where you might have multiple find()'s) the newly switched data source will persist until switched back. Therefore it is important to remember to “reset” your data source once you are done with the read operation(s).

Now, here’s the basic usage sample (this snippet would be inside of our getListofPosts() method):

//let's switch our DS to replica
$this->_switchDataSource(array(
  'Post', 'PostsTag', 'Author', 'PostRating'
), 'replica');

//now we can execute our find with all of the above
//model data coming from the read-only DB
$posts = $this->find('all', array(
  'contain' => array(
  //include our models here
  ),
  'limit' => 35
));

//don't forget to switch the DS back to default
$this->_switchDataSource(array(
  'Post', 'PostsTag', 'Author', 'PostRating'
));

return $posts;

As you see the implementation is pretty simple. The only thing to keep in mind is that if you are getting some SQL errors, chances are you have not included all of the required models for the operation, the most common case is forgetting the join table model. To troubleshoot and see the results the debug kit is very helpful, because it will show you which data source is being used to run a particular set of queries.
Another hint, if you use the same set of models over and over you might as well assign them to a property in your model, so that if you need to add or change something you’d only do it in one place.
Using our example we can modify the code like so:

public $postQueryModels = array('Post', 'PostsTag', 'Author', 'PostRating');

....

$this->_switchDataSource($this->postQueryModels, 'replica');

If you use UUID’s…

Be extra careful to make sure that, according to convention, your ‘id’ column (or primary key) is:

char(36) and never varchar(36)

CakePHP will work with both definitions, however you will be sacrificing about 50% of the performance of your DB (MySQL in particular). This will be most evident in more complicated SELECT’s, which might require some JOIN’s or calculations.

find(‘first’)… gotcha

Just a simple tip…

Here’s a typical find() example:

$this->Post->find('first', array('condition' => array('id' => 5)));

Why does it return me the post with ID = 1, rather than ID = 5?

Of course, a careful reader spotted the spelling issue in the key:
‘condition’ instead of ‘conditions’.

Moral of the story is that find('first') can be a little tricky and misleading and if you are getting an unexpected result be sure to double-check your implementation.

find('first', 'blah');

Installing membase/memcached

These are instructions for getting membase/memcached installed in your local environment.
(I was installing on windows so your setup might be a little different, but general approach is mostly the same).

1. Download the membase server for your OS (community edition is the one you are after):
http://www.couchbase.com/downloads

Once downloaded, the installation is quite simple. Just follow the prompts.
After the server installation is complete, your default browser should open and you will be prompted to continue setting up the “cache bucket”.

Again, it is a simple step by step process. The only thing you’d want to make sure is that you select memcached bucket type.
(This will be 100% compatible with any other default installation).

2. Make sure you have memcache support enabled in your PHP install.
The easiest way to find out is to check the output of phpinfo(). Look for the “memcache” section.

3. What to do if you don’t see the section mentioned above?
First, check your php.ini for: extension=php_memcache.dll (php_memcache.so, on *nix platforms).
In some cases you just might need to uncomment the line above.

Next, you should have an entry for the php/memcached settings, it should look something like this:


[Memcache]
memcache.allow_failover = 1
memcache.max_failover_attempts = 20
memcache.chunk_size = 8192
memcache.default_port = 11211

In my case the php_memcache.dll was missing, so I downloaded the appropriate version from:
http://code.google.com/p/thinkam/downloads/detail?name=php_memcache-cvs-20090703-5.3-VC6-x86.zip&can=2&q=

You’ll have to place the .dll or .so with the rest of your php extensions; if your are using XAMPP it will be somewhere like:
c:\xampp\php\ext

4. Configure cake
Open up app/config/core.php and scroll all the way down to the cache settings.
Presuming you’ve installed everything with all defaults, all you’d have change is:
'engine' => 'File' to 'engine' => 'Memcache'

5. Restart your web server.
You might want to clear your model cache first, to be sure that cake doesn’t fall back on File caching if something isn’t configured properly.
If all goes well, you should see no warnings, nothing should be written to the file system, and in your membase console (Monitor -> Data Buckets -> default) you should begin to see some activity.
(Chart should be updated, as well you should see some records under TOP KEYS).

Congrats, you are now using memcached as your caching mechanism.

Good luck!

Speed up your pagination with a simple hack…

Before I go into the example in this little post, let me just say that this situation won’t be applicable to everyone…

But let’s consider the following:
We have a table with tens of thousands of records, that need to be paginated.

As you know, cake will execute two queries; first to get the count of total records, second to get the actual records.

The questions one might ask are:
“Would any user really go through 5 thousand pages to find what they are looking for?”
“If I bring the last 1,000 records wouldn’t that be enough for a vast majority of needs?”
“How many pages in Google do you go through, when searching for something, before you give up?”
“Is it not better to provide filters, or search tools to help your users narrow down the results to something manageable?”

If you’ve answered “Yes” to two or more questions, please consider the hack…

In your model, which needs to be paginated, do the following:

public function paginateCount($conditions = null,
                                 $recursive = 0,
                                 $extra = array()) {
   return 1000;
}

Yep, we are overriding paginateCount() and simply returning 1,000 because we know that this will be the maximum amount of records that our paginator needs to know about.
Depending on how complex the underlying query is (for example you might have JOIN’s or various conditions, which would usually need to be taken into the account in your typical count query), the above hack can dramatically increase the performance of your pagination.

Dealing with static pages v2 (or… 3?)

Over the years of cake development we’ve seen a number of ways to get rid of the the /pages/ path in the URL for static pages.

By default if you create an “about us” page, such as in app/views/pages/about.ctp, the resulting URL would be:
www.example.com/pages/about

I’m sure you’ve seen a ton of complaints and solutions about how to get rid of this seemingly “annoying” /pages/
To keep the URL’s clean, most people would obviously prefer to have www.example.com/about instead. (No /pages/ in sight).

Recently, my favorite solution has become the following setting in the routes.php:

$staticPages = array(
		'about',
		'legal',
		'policy',
		'something'
);

$staticList = implode('|', $staticPages);

Router::connect('/:static', array(
		'plugin' => false,
		'controller' => 'pages',
		'action' => 'display'), array(
				'static' => $staticList,
				'pass' => array('static')
				)
		);

Go ahead, create your about.ctp and then attempt to access it by going to www.yoursite.com/about

The Router will nicely remove the /pages/ from the URL.

Q.
Why keep the pages in the array() and then use implode()? Couldn’t I just have the following?

$staticList = 'about|legal|policy|something';

A.
You sure could, but array structure allows to keep things organized neater and if you have a large amount of pages it’s easier to scan visually to insert/remove pages as necessary. Either way, you have both options to work with.

Be mindful of the redirect(s)

Just a couple of tidbits about CakePHP redirects, prefix routing and Auth.

1. Auth mysteriously redirects your logged in user into the abyss…

After you’ve checked all your setting in beforeFilter()'s of App Controller and relevant Controllers, it still seems like a completely bizarre situation where all of a sudden your well-authenticated users get completely kicked out of their “designated” area.
One more thing to check is any element (which is called from an “Authed” view) that might be using requestAction() from some Controller, which had not been granted the right privileges. To explain in more detail, requestAction() might attempt to access information (action) from a slightly unrelated controller, to which the current user has not been granted any permission. At this point the Auth component will kick-in and do its job by redirecting the user to a homepage or other “strange” location.
This one is always tricky to spot, since the bugger is hiding in the view/element, yet behaves as though something that should be taking place in your controllers.

2. Be explicit about your prefix routing destinations

If you have more than one routing prefix, such as “user” and “admin”, there are a few ways to move from one “prefixed” area to another.

For example:

$this->redirect(array(
   'controller' => 'users',
   'action' => 'something',
   'admin' => false
));

In many cases this will get you out of the “admin” area and move to the Users Controller.
However, depending on other (routing) issues, a more detailed instruction would be:

$this->redirect(array(
  'controller' => 'users',
  'action' => 'something',
  'admin' => false,
  'plugin' => false,
  'user' => true
));

The above situation might happen in case your something action is actually an alias of prefixed user_ action (such as user_details).
The plugin key is not as common, but also good to keep in mind.

Two ways to debug your AJAX queries

Every once in a while you might have a need to trigger an AJAX action in the controller, which should return some results from the server…

When everything works, well it is certainly all fine and dandy, but if things go haywire, first thing to check out is the response given by the server… and more importantly why does it not match your expectations. One of the more typical questions is: “What query was executed on the DB, during the AJAX call”?…
Because AJAX calls are not easily logged to any console, sometimes it becomes a little troublesome to figure out.

When using CakePHP you have two methods to see what’s happening on the DB level.

1. If you use debug kit

There is one little known, but nice feature.
After you load the page, which executes some AJAX request… do the following:
1. Click the “History” tab
2. In the list you should see the given AJAX call to some controller action
3. Click it
4. Switch over the to the SQL log tab and see what has happened.
(The background of the queries should be mild green, which will signify that you are looking at a previous request in the “history” of calls)

… amazing feature indeed. Yet, unfortunately, it doesn’t work 100% of the time. (I wish I had a more in-depth answer as to why).

2. Do not despair, there is another way to log your SQL dealings with a few lines of code.

Using the same scenario as above, in your action which is being triggered by AJAX from the client, add the following snippet:

$db = ConnectionManager::getDataSource('default');
debug($db->getLog());

Now, if you are using firebug (or similar tool, which I hope you certainly do)… You’ll see the exact query and some other debug info generated by the cake’s DB drivers.

p.s. Thanks to jrbasso, for this wonderful and helpful hint.