CakeDC Blog

TIPS, INSIGHTS AND THE LATEST FROM THE EXPERTS BEHIND CAKEPHP

Benchmarking requestAction

Now there has been a lot of discussion in the past few months about requestAction() and how it can very easily create a negative impact on your application. In fact I even wrote such an article myself. However, its high time that someone did the number crunching to really see if requestAction() is actually as slow as we all seem to think it is. So onto the testing method and the results.

Testing method

To test this theory I used a small CakePHP application and the SVN head (revision 8064) of CakePHP. I used a simple sample application with 2 controllers and 2 models. My model method directly returned the results without touching the database, so that database retrieval time and model processing would not be a factor in these tests. As I was only interested in the performance implications inherent in requestAction() itself, I wanted to remove the variance created by connecting to a database. I set debug = 0, and used basic file caching. After warming up the cake core caches, I tested 4 different controller actions.

  • Using Relations / ClassRegistry::init() - The method I originally proposed, and often touted as the 'best' solution to requestAction()
  • Using RequestAction with a string URL
  • Using RequestAction with and Array URL
  • Using a cached RequestAction - This more accurately simulates how we use requestAction at CakeDC.

Benchmarks were generated with Siege I used 10 concurrent users with 110 reps each. My local development web-server is running Apache 2.2/PHP 5.2.6 o n a 2.6GHz Core 2 Duo iMac with 2GB of ram. I ran each test 3 times and took the best result of each.

Using model relations / ClassRegistry::init()

First up was my originally proposed solution of using model relations to access the correct information. I used the following command and got the following results.

siege -b http://localhost/benchmark/posts/using_relations

Transactions:		        1100 hits
Availability:		      100.00 %
Elapsed time:		       63.21 secs
Data transferred:	        1.50 MB
Response time:		        0.55 secs
Transaction rate:	       17.40 trans/sec
Throughput:		        0.02 MB/sec
Concurrency:		        9.60
Successful transactions:        1100
Failed transactions:	           0
Longest transaction:	        1.76
Shortest transaction:	        0.10


Using RequestAction with a string URL

Up next was using request action with a string url. String URL's are often the slower way to perform a requestAction as parsing the URL string is one of the more expensive operations in request dispatching. I used the following command and the best results were.

siege -b http://localhost/benchmark/posts/using_requestaction

Transactions:		        1100 hits
Availability:		      100.00 %
Elapsed time:		       64.60 secs
Data transferred:	        1.51 MB
Response time:		        0.57 secs
Transaction rate:	       17.03 trans/sec
Throughput:		        0.02 MB/sec
Concurrency:		        9.72
Successful transactions:        1100
Failed transactions:	           0
Longest transaction:	        1.76
Shortest transaction:	        0.11


RequestAction with an Array URL

Up next is requestAction() witn an array url. Using an array URL is supposed to expedite the dispatching process as it bypasses much of the parameter parsing done by Router. This theory turned out to be true, as Array URL's clocked in marginally faster than their string counterparts.

siege -b http://localhost/benchmark/posts/using_requestaction_array

Transactions:		        1100 hits
Availability:		      100.00 %
Elapsed time:		       64.08 secs
Data transferred:	        1.53 MB
Response time:		        0.57 secs
Transaction rate:	       17.17 trans/sec
Throughput:		        0.02 MB/sec
Concurrency:		        9.78
Successful transactions:        1100
Failed transactions:	           0
Longest transaction:	        1.66
Shortest transaction:	        0.11

RequestAction using Array URL's and Caching

In my mind this was going to be the most performant requestAction option, due to the cached nature. The results were as expected with this method clocking to be only slightly behind the relation call. It is important to note as well, that this test does not reflect the time savings earned from not having to make an additional query/ round of result parsing. In a real world situation, the savings of using a cached element would be magnified by the cost of the query.

siege -b http://localhost/benchmark/posts/using_cached_requestaction

Transactions:		        1100 hits
Availability:		      100.00 %
Elapsed time:		       63.60 secs
Data transferred:	        1.52 MB
Response time:		        0.56 secs
Transaction rate:	       17.30 trans/sec
Throughput:		        0.02 MB/sec
Concurrency:		        9.62
Successful transactions:        1100
Failed transactions:	           0
Longest transaction:	        1.77
Shortest transaction:	        0.09

Results Summary

In case you quickly scanned through the full results here is a summary of what happened.

Method Requests per second (mean) Total time taken (seconds)
Using relations/ClassRegistry::init() 17.40 63.21
Using requestAction and string urls 17.03 64.60
Using requestAction and array urls 17.17 64.08
Using cached requestaction 17.30 63.60

In closing requestAction() can be slower than a direct method call. There are some benefits to using requestAction though.

  • You have the opportunity to reduce the number of repeated lines of code by putting the requestAction inside the element. In doing so, you create an encapsulated element, that can be included anywhere without having to worry about having the correct method calls in your controller.
  • You can more easily cache the element. By using requestAction in conjunction with element caching you have an easy to use, simple to implement caching. Getting the same results with model method calls in your controller requires additional caching logic in your models.
  • The potential for increased performance. As we saw in the benchmarks above, a cached element performed almost as fast as the direct method call. This margin will grow when a database query is added into the mix.

Now am I retracting my previous stance on requestAction? No, I still feel that there are many situations where requestAction is the incorrect solution and signals poor application design. However, when the need arises it is good to know that requestAction can be as fast or faster than other approaches when implemented properly.

You can download all the files I used for this process and run your own tests as well.

Latest articles

CakePHP API Plugin

Are you creating an API in CakePHP? This task looks very popular these days, and most of our clients need an API to expose certain services to their own rich client applications, or third party services. Even if it's easy to configure CakePHP to expose a REST API, and there are other plugins that could help you building an API, we found ourselves working on specific tweaks per project to adjust the way the API was designed, so we decided to wrap all these ideas and create a specific CakePHP API Plugin including

  • Services definition
  • Integrated CRUD
  • Nested resources
  • Pagination
  • Sorting
  • Associations
  • Versioning
  • Custom Extensions (data format / transformers)
  • Self documentation
We've gathered all the best practices around API building and CakePHP and wrapped them into an easy to install and setup Plugin to be used as the foundation of your API intensive CakePHP projects. Let's walkthru some of the Plugin features using an example application: the bookmarker tutorial http://book.cakephp.org/3.0/en/tutorials-and-examples/bookmarks/intro.html We'll assume you've already created a new CakePHP application and configured it to use the bookmarker database (schema dump here http://book.cakephp.org/3.0/en/tutorials-and-examples/bookmarks/intro.html#creating-the-database).

Setting up the CakePHP API Plugin

Download the plugin first composer require cakedc/cakephp-api:dev-master Then ensure plugin is loaded in you bootstrap.php file Plugin::load('CakeDC/Api', ['bootstrap' => true, 'routes' => true]);

Now you have an API!

Test your newly configured "default" API using curl curl -X GET http://bookmarker.dev/api/bookmarks You'll get something similar to: { "status": "success", "data": [], "pagination": { "page": 1, "limit": 20, "pages": 0, "count": 0 }, "links": [ { "name": "self", "href": "http:\/\/bookmarker.dev\/api\/bookmarks", "rel": "\/api\/bookmarks", "method": "GET" }, { "name": "bookmarks:add", "href": "http:\/\/bookmarker.dev\/api\/bookmarks", "rel": "\/api\/bookmarks", "method": "POST" } ] } If you look at the provided output you'll identify we've used a JSend default renderer (status, data) and we append some extra data under 'links' (HATEOAS dynamically generated for your CRUDs) and pagination. The specific "extensions" used can be configured and custom extensions created for your specific needs, see https://github.com/CakeDC/cakephp-api/blob/master/docs/Documentation/extensions.md We'll publish a couple tutorials soon covering some of the features implemented, and explaining how did we use the CakePHP API Plugin to address specific use cases. Meanwhile, please check the documentation here https://github.com/CakeDC/cakephp-api/blob/master/docs/Documentation/overview.md

Giving back to the community

This Plugin's development has been sponsored by the Cake Development Corporation. Contact us if you are interested in:  

Create Google app for web oauth2 login step by step

Here's a step by step tutorial about how to create a web oauth2 app in Google dashboard.

Google app oauth login app 1
  • Add some cool name for your new Google app project and click "Create"
Google app oauth login app 2
  • Under "Library" section, create a new Google+ API project
Google app oauth login app 3
  • Click "Enable" in the dashboard tab
Google app oauth login app 4
  • Under "Credentials" menu, click "Oauth consent screen" tab and enter some cool name to be displayed to users when requesting their access to your application. Then click "Save".
Google app oauth login app 5
  • Under "Credentials" menu, click "Create credentials" and select "Oauth client ID".
Google app oauth login app 6
  • Now click "Web application" radio, and type your domain name and oauth callback
    • Under "Authorized Javascript origins", add your domain name: mydomain.com
    • Under "Authorized redirect URIs", add all the allowed callback url's to your application. For example if you are using CakeDC/Users Plugin, you'll need to add mydomain.com/auth/google
  • Then click "Save"
Google app oauth login app 7
  • Copy the Iauth client and secret id's into your application configuration
  • Be careful, some browsers will append blank spaces to the codes, remove any extra blank space (trim)
Google app oauth login app 8
  • Ensure the API is enabled, you can test your application now and check there is "Traffic" displayed
  You have now a Google app configured to provide Oauth2 login to your web application. Enjoy!                  

Login with Google Oauth2 in CakePHP using CakeDC/Users Plugin

This article is inspired by this question in Stack Overflow and belongs to a series of articles describing the step by step tutorial to configure CakeDC Users Plugin with the most commonly used Oauth2 providers, in this case we'll configure Google login. We'll assume you have a working CakePHP application with no Auth configured yet.

Setup

Use composer to install the CakeDC Users Plugin and the required oauth2 providers To be able to configure the callbacks in Google dashboard, you'll need to create a virtual host for you application. You don't need a working domain name, you could use something like "mydomain.dev" but Google requires a domain name (no localhost). composer require cakedc/users:@stable composer require league/oauth2-google:@stable Load it from your bootstrap.php file Plugin::load('CakeDC/Users', ['routes' => true, 'bootstrap' => true]); Run migrations to add 2 new tables: 'users' and 'social_accounts' bin/cake migrations migrate -p CakeDC/Users

Configuration

Load the Component in your src/Controller/AppController.php public function initialize() { parent::initialize(); // // ... // $this->loadComponent('CakeDC/Users.UsersAuth'); }

Create a new Google application

<?php // /config/users.php file contents $config = [ 'Users.Social.login' => true, 'OAuth.providers.google.options.clientId' => 'CLIENT_ID_HERE', 'OAuth.providers.google.options.clientSecret' => 'SECRET_HERE', ]; return $config;
  • Modify your bootstrap.php file to ensure the config file is loaded this way
Configure::write('Users.config', ['users']); //add this line before Plugin::load('CakeDC/Users... Plugin::load('CakeDC/Users', ['routes' => true, 'bootstrap' => true]); This file will override any configuration key present in the Plugin, you can check the configuration options here Configuration. Now you are ready to go to your login page and click "Sign up with Google". Upon successful login, a new user will be created in your users table and related oauth2 tokens will be saved in the social_accounts table. The new user created will have the "user" role (by default, but customizable). And based on your Auth rules, this user will be able to access your site. You are done!

Read more about CakeDC Users Plugin

Giving back to the community

This Plugin's development has been sponsored by the Cake Development Corporation. Contact us if you are interested in: We hope you've enjoyed this short tutorial covering the Google login, stay tunned for new CakePHP + Users Plugin tutorials coming soon...

BOOK A 15 MINUTES FREE
CONSULTING WITH US:
We Bake with CakePHP