CakeDC Blog

TIPS, INSIGHTS AND THE LATEST FROM THE EXPERTS BEHIND CAKEPHP

CakePHP 3.0 is coming

For those of you who may have missed it, this week we released the first alpha of CakePHP 3.0, with a significant update to begin our move towards beta. I'm really excited to see how the project is evolving, and the amazing work that the members of the core team are doing, as well as what all those contributing are helping to build. But its important to look back in retrospect, and understand from where we've come.

Baking the Cake

If you're not aware, CakePHP has now been almost 10 years in the making. That's a long time for a project to stay as active as it has. Everyone has their favorite framework, and some like a few more than others, but one thing that's clear in my mind is that CakePHP has always been very popular, even until today.

The project started when I teamed up with Michal Tatarynowicz, who had created the basic feature set of what would become CakePHP. I had begun work on what is currently the model layer in the pre 3.0 version of the framework, and continued leading the project when Michal left shortly after we open sourced under the MIT license. This was back in 2005, and working with PHP 4. Back then we had to work around the language a lot, as it was lacking the object oriented features which we now all take for granted. We had to emulate or actually build out many of the native aspects now included with PHP, which made the task all the more complicated. Don't get me wrong, it was fun times, as the language was growing fast and we were all pushing it along. It's no secret the Rasmus isn't a huge fan of frameworks, but like Rails for Ruby, many of the frameworks for PHP have also helped the language gain a place in many people's hearts.

But time goes by, and like all things, PHP grew up and matured as a language. A lot of the features we had implemented for CakePHP in PHP 4 now became native with PHP 5, so although we'd provided the solutions when they weren't available, these now became redundant. But people and hosting companies were slow to adopt. The framework had grown a large community by then, so it was difficult for us to just drop support for PHP 4 and leave them without their framework. It was also in our interest to support PHP by prompting people to upgrade, so we took the middle road. This is where our infamous backwards compatibility for PHP 4 stems from.

There were disagreements between core members of the project, where some advocated for jumping the gun and releasing a version which required the latest version of PHP, but I refused to allow our community to be left behind. These are people who had grown up with the framework, people who relied on us to keep a solution which allowed anyone to use it. In hindsight you could say that those developers weren't worth supporting, but I see our community as a family, and like my Marine training taught me, no man gets left behind.

However, the years past, and we went from 1.2 to 1.3, and CakePHP begun to mature into a powerful solution for rapid application development. We also saw how adoption for PHP 5 improved, and hosts begun to offer broad support, which is when we decided to make the move to PHP 5.2 with the release of CakePHP 2. There were mixed feelings about the decision to not jump straight to 5.3, but I still feel today that, in allowing the framework to mature as it has on a stable code base, people who have counted on us would hopefully understand that choice.

Growing up as a Community

Like the years that have come before us, we all grow up as developers, and PHP the language grows with us. The impulse we've seen over the past years with the releases of 5.3, 5.4 and 5.5 have shown how the community can really build a powerful language. But it's not only the language that grows, but the community around it as well. We've seen over the past years how interoperability between frameworks has become a requirement, and the technical expectations of developers have become consistently more demanding. We've seen how the rise of packages managers, like Composer, have facilitated this distributed and modular approach to building PHP applications. So when we looked at what we expected for 3.0, as Jose Lorenzo said in the technical keynote at CakeFest, our annual conference, "we're all older and wiser", so it's time to put those years of experience to good use.

So, for CakePHP 3.0 we decided that now is a good time to take our community and move everything towards a stronger and brighter future. This means that we've made some of the important decisions, which align the framework with the coming features in the language, and provide the same framework goodness people are used to, but deliver it with new features which upgrade the solution for another 10 years to come. This also means breathing new life into many of the core aspects of the framework, which in some cases have become its winning features, and in others the infamous trademarks of CakePHP.

I invite you all, those who love CakePHP and even those who don't, to give this alpha of the latest major version of the framework a try, and let us know how well it tastes. We hope that this is the beginning of a great new chapter in the history of CakePHP, and one which lets us grow further, and together, as a community. Thank you.

Latest articles

Integrating Users and ACL plugins in CakePHP

In previous posts, we saw how CakeDC Users plugin can help you to build an application that manages everything related to users: registration, social login, permissions, etc. Recently it has been noted by the team that there are some use cases where a deeper control of permissions is needed - more than is offered in RBAC. Today we’ll go into this using the ACL approach. ACL or Access Control List, refers to the application using a detailed list of objects to decide who can access what. It can be as detailed as particular users and rows through to specifying which action can be performed (i.e user XX has permissions to edit articles but does not have permissions to delete articles). One of the big features of ACL is that both the accessed objects; and objects who ask for access, can be organized in trees. There’s a good explanation of how ACL works in the CakePHP 2.x version of the Book. ACL does not form part of CakePHP core V 3.0 and can be accessed through the use of the cakephp/acl plugin. Let’s just refresh the key concepts of ACL:

  • ACL: Access Control List (the whole paradigm)
  • ACO: Access Control Object (a thing that is wanted), e.g. an action in a controller: creating an article
  • ARO: Access Request Object (a thing that wants to use stuff), e.g. a user or a group of users
  • Permission: relation between an ACO and an ARO
For the purpose of this article - we shall use this use case: You are using CakeDC/users plugin and now want to implement ACL in your application.

Installation

Starting with a brand new CakePHP app: composer selfupdate && composer create-project --prefer-dist cakephp/app acl_app_demo && cd acl_app_demo We are going to use CakeDC/users and cakephp/acl plugins. In a single step we can install them with composer: composer require cakedc/users cakephp/acl Create a DB and set its name and credentials in the config/app.php file of the just created app (in the Datasources/default section). This command can help you out if you are using MySQL: mysql -u root -p -e "create user acl_demo; create database acl_demo; grant all privileges on acl_demo.* to acl_demo;" Plugins will be loaded always with the app. Let’s set them on the bootstrap file: bin/cake plugin load -br CakeDC/Users
bin/cake plugin load -b Acl Now let’s insert a line in bootstrap.php before Users plugin loading, so cakedc/users will read the configuration from the config/users.php file of our app. Configure::write('Users.config', ['users']); This file does not exist yet. The plugin provides a default file which is very good to start with. Just copy it to your app running: cp -i vendor/cakedc/users/config/users.php config/ Also, let’s copy the permissions file the same way to avoid warnings in our log files: cp -i vendor/cakedc/users/config/permissions.php config/ We need to change cakedc/users config: remove RBAC, add ACL. In cakephp/acl there’s ActionsAuthorize & CrudAuthorize. We’ll start just using ActionsAuthorize. We will tell ActionsAuthorize that actions will be under the 'controllers/' node and that the users entity will be MyUsers (an override of the Users entity from the plugin). Edit the Auth/authorize section of config/users.php so that it sets: 'authorize' => [ 'CakeDC/Auth.Superuser', 'Acl.Actions' => [ 'actionPath' => 'controllers/', 'userModel' => 'MyUsers', ], ], Add calls to load components both from Acl & Users plugin in the initialize() method in AppController: class AppController extends Controller { public function initialize() { parent::initialize(); // (...) $this->loadComponent('Acl', [ 'className' => 'Acl.Acl' ]); $this->loadComponent('CakeDC/Users.UsersAuth'); // (...) } // (...) }

Database tables

Some tables are required in the database to let the plugins work. Those are created automatically just by running their own migrations: bin/cake migrations migrate -p CakeDC/Users
bin/cake migrations migrate -p Acl One table from the Acl plugin needs to be fixed because Users migration creates users.id as UUID (CHAR(36)) and Acl migrations creates AROs foreing keys as int(11). Types must match. Let’s fix it adapting the aros table field: ALTER TABLE aros CHANGE foreign_key foreign_key CHAR(36) NULL DEFAULT NULL; Now, it’s time to set our own tables as needed for our app. Let’s suppose we are developing a CMS app as specified in the CMS Tutorial from the CakePHP book. Based on the tutorial, we can create a simplified articles table: CREATE TABLE articles ( id INT AUTO_INCREMENT PRIMARY KEY, user_id CHAR(36) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL, title VARCHAR(255) NOT NULL, body TEXT, published BOOLEAN DEFAULT FALSE, created DATETIME, modified DATETIME, FOREIGN KEY user_key (user_id) REFERENCES users(id) ); Note: Specify CHARACTER SET and COLLATE for user_id only if the table CHARACTER SET and COLLATE of the table differ from users.id (than may happen running migrations). They must match. Roles will be dynamic: admin will be allowed to manage them. That means that they has to be stored in a table. CREATE TABLE roles ( id CHAR(36) NOT NULL PRIMARY KEY, name VARCHAR(100) NOT NULL, created DATETIME, modified DATETIME ); Association between users and roles bill be belongsTo, so we’ll need a foreign key in the users table instead of a role varchar field: ALTER TABLE users ADD role_id CHAR(36) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL AFTER role, ADD INDEX role_id (role_id), ADD FOREIGN KEY (role_id) REFERENCES roles(id); ALTER TABLE users DROP role;

Baking

Time to think about what will be ACOs and AROs. In most cases, Users will be the only AROs. To do that, we need to link the Users entity and table to the ACL plugin. In this case that we are using CakeDC/users plugin, we first need to extend the plugin as it is explained in the docs. We will also add the behavior and parentNode() as shown in the cakephp/acl readme file, so at the end we’ll need to create those files: src/Model/Entity/MyUser.php: <?php namespace App\Model\Entity; use CakeDC\Users\Model\Entity\User; /** * Application specific User Entity with non plugin conform field(s) */ class MyUser extends User { public function parentNode() { return ['Roles' => ['id' => $this->role_id]]; } } src/Model/Table/MyUsersTable.php: <?php namespace App\Model\Table; use CakeDC\Users\Model\Table\UsersTable; class MyUsersTable extends UsersTable { public function initialize(array $config) { parent::initialize($config); $this->addBehavior('Acl.Acl', ['requester']); $this->belongsTo('Roles'); $this->hasMany('Articles'); } } Run bin/cake bake controller MyUsers (beware of case) Then, edit the top of src/Controller/MyUsersController.php as: <?php namespace App\Controller; use App\Controller\AppController; use CakeDC\Users\Controller\Traits\LinkSocialTrait; use CakeDC\Users\Controller\Traits\LoginTrait; use CakeDC\Users\Controller\Traits\ProfileTrait; use CakeDC\Users\Controller\Traits\ReCaptchaTrait; use CakeDC\Users\Controller\Traits\RegisterTrait; use CakeDC\Users\Controller\Traits\SimpleCrudTrait; use CakeDC\Users\Controller\Traits\SocialTrait; class MyUsersController extends AppController { use LinkSocialTrait; use LoginTrait; use ProfileTrait; use ReCaptchaTrait; use RegisterTrait; use SimpleCrudTrait; use SocialTrait; // CRUD methods ... To generate the template files for MyUsers we can run: bin/cake bake template MyUsers Next, just let Cake bake all objects for articles and roles: bin/cake bake all Articles
bin/cake bake all Roles Add behavior to their tables. ArticlesTable will act as controlled because it will represent ACOs: class ArticlesTable extends Table { public function initialize(array $config) { parent::initialize($config); // (...) $this->addBehavior('Acl.Acl', ['controlled']); // (...) The case of RolesTable will be similar but it will act as requester, as it will represent AROs: class RolesTable extends Table { public function initialize(array $config) { parent::initialize($config); // (...) $this->addBehavior('Acl.Acl', ['requester']); // (...) Create the parentNode() method in both entities: Article and Role. public function parentNode() { return null; }

Testing

Ok, time to test the whole system! At this point, the app should be ready to use. At least, for an administrator. Let’s quickly create one: it is as easy as running bin/cake users add_superuser. New credentials will appear on screen. When accessing our app in the URL that we installed it, a login form will appear. Log as the just created admin. First, let’s create some roles. Go to /roles in your app’s URL. Then, click on "New Role". Create the roles:
  • Author
  • Editor
  • Reader
Then, we can create two users an author and a reader. Head to /my-users and add them. Remember to select the Active checkbox and the proper role in the dropdown menu. Because MyUsers has the AclBehavior, AROs has been automatically created while creating users, along with the created roles. Check it out with bin/cake acl view aro Aro tree: --------------------------------------------------------------- [1] Roles.24c5646d-133d-496d-846b-af951ddc60f3 [4] MyUsers.7c1ba036-f04b-4f7b-bc91-b468aa0b7c55 [2] Roles.5b221256-0ca8-4021-b262-c6d279f192ad [3] Roles.25908824-15e7-4693-b340-238973f77b59 [5] MyUsers.f512fcbe-af31-49ab-a5f6-94d25189dc78 --------------------------------------------------------------- Imagine that we decided that authors will be able to write new articles and readers will be able to view them. First, let’s create the root node for all controllers: bin/cake acl create aco root controllers Then, let’s inform ACL that there are such things as articles: bin/cake acl create aco controllers Articles Now, we will tell that there are 5 actions related to Articles: bin/cake acl create aco Articles index bin/cake acl create aco Articles view bin/cake acl create aco Articles add bin/cake acl create aco Articles edit bin/cake acl create aco Articles delete We can see the first branch of the ACOs tree here: bin/cake acl view aco Aco tree: --------------------------------------------------------------- [1] controllers [2] Articles [3] index [4] view [5] add [6] edit [7] delete --------------------------------------------------------------- ACL knows that articles can be added, so let’s tell who can do that. We can check which aro.id belongs to role Author with: mysql> select id from roles where name like 'Author'; +--------------------------------------+ | id | +--------------------------------------+ | 24c5646d-133d-496d-846b-af951ddc60f3 | +--------------------------------------+ 1 row in set (0.00 sec) And the same with the Reader role:: mysql> select id from roles where name like 'Reader'; +--------------------------------------+ | id | +--------------------------------------+ | 25908824-15e7-4693-b340-238973f77b59 | +--------------------------------------+ 1 row in set (0.00 sec) So, if we look up this id in the bin/cake acl view aro output, it turns out that aro.id 1 is Author and that aro.id 3 is Reader. If we want to let authors (ARO 1) add articles (ACO 5), we must grant permission to Articles/add to editors by running: bin/cake acl grant 1 5 And we'll grant readers (ARO 3) view articles (ACO 4) with: bin/cake acl grant 3 4 Don't forget to grant access to Articles/index for all roles, or nobody would access /articles: bin/cake acl grant 1 3 bin/cake acl grant 2 3 bin/cake acl grant 3 3 Note: Obviously, it would be easier to set a "super role" which includes the 3 roles and grant access to index to it, but we don't want to add too many steps in this tutorial. You can try it for yourself. Then, aros_acos table becomes: mysql> select * from aros_acos; +----+--------+--------+---------+-------+---------+---------+ | id | aro_id | aco_id | _create | _read | _update | _delete | +----+--------+--------+---------+-------+---------+---------+ | 1 | 1 | 5 | 1 | 1 | 1 | 1 | | 2 | 3 | 4 | 1 | 1 | 1 | 1 | | 3 | 1 | 3 | 1 | 1 | 1 | 1 | | 4 | 2 | 3 | 1 | 1 | 1 | 1 | | 5 | 3 | 3 | 1 | 1 | 1 | 1 | +----+--------+--------+---------+-------+---------+---------+ 5 rows in set (0.00 sec) Let’s create a new article as the first user. To do that:
  • Log out (we are still logged in as superadmin) going to /logout
  • Log in as the first created user
  • Go to /articles
  • Create an article
Right now, author can add an article but not view it, since we only set the add permission. Check it out clicking in View next to the article. Log in as a reader to check how the reader can really view the article. Obviously, more than a couple of permissions have to be grant in a big app. This tutorial served just as an example to start.

Last words

That's all for now related to the use of ACL in a webapp made with CakePHP. A lot more can be done with ACL. Next step would be to use CrudAuthorize to specify which CRUD permissions are granted for any ARO to any ACO. Keep visiting the blog for new articles! This tutorial has been tested with:
  • CakePHP 3.5.10
  • CakeDC/users 6.0.0
  • cakephp/acl 0.2.6
An example app with the steps followed in this tutorial is available in this GitHub repo. Please let us know if you use it, we are always improving on them - And happy to get issues and pull requests for our open source plugins. As part of our open source work in CakeDC, we maintain many open source plugins as well as contribute to the CakePHP Community. Reference

Using a vagrant box as quick environment for the Getting Started with...

We've decided to create a simple vagrant box with all the required packages to improve the environment setup step in our free Getting Started with CakePHP training session. We used other tools in the past, but we hope vagrant will help users to install a common environment before the session to get the most of it.

Requirements

Setup

  • Create a new folder where the code will be located
  • Create a new file called Vagrantfile with the following contents
# -*- mode: ruby -*- # vi: set ft=ruby : Vagrant.configure("2") do |config| config.vm.box = "cakedc/cakephp-training" config.vm.network :forwarded_port, guest: 8765, host: 8765 config.vm.provider "virtualbox" do |vb| vb.memory = "1024" vb.customize ['modifyvm', :id, '--cableconnected1', 'on'] end end
  • Run vagrant up
  • Wait (download could take several minutes depending on your internet connection)
  • Run vagrant ssh
Now you have ssh access to a training ubuntu (16.04) based virtual machine, with all the requirements to run your training CakePHP application.
  • Setup a new CakePHP project
cd /vagrant composer create-project cakephp/app
  • Start the local server
cd /vagrant/app php bin/cake.php server --host 0.0.0.0
  • From your host machine, open a browser and navigate to http://localhost:8765
  • You should be able to see the CakePHP welcome page
  We think this VM will enable faster environment setups, and an easier entry point to the training session. Please let us know if you find issues with this process.

Boosting your API with CakePHP API and PHP-PM (ReactPHP)

A couple days ago AlexMax commented in CakePHP's IRC channel about the https://github.com/php-pm/php-pm project and it rang a bell for us. We did a couple tests internally and found this could be a great companion to our API plugin, so we wrote a new Bridge for CakePHP and ran some benchmarks.

The Cast

We put all together and created a sample application (1 posts table with 30 records) to do some benchmarks.

Benchmark configuration

We are not aiming to provide detailed or production figures, just a reference of the results obtained for your comparison. Results are generated from a development box, using PHP 7.1.12-3+ubuntu16.04.1+deb.sury.org+1 with xdebug enabled on ubuntu xenial, 8x Intel(R) Core(TM) i7-4771 CPU @ 3.50GHz We baked the application using the latest CakePHP 3.5.10, and set application debug to false, and log output to syslog. As we are interested in boosting API response times the most, we tested the following scenarios
  • A) CakePHP json output, served from nginx+phpfpm
  • B) CakePHP + API Plugin Middleware integration json output, served from nginx+phpfpm
  • C) CakePHP + API Plugin Middleware integration json output, served from php-pm
Benchmark figures were obtained using ab -n 5000 -c 100 URL

Results

Scenario requests/second avg time
A) CakePHP json output, served from nginx+phpfpm using php7.1 372.97 [#/sec] (mean) 268.120 [ms] (mean)
B) CakePHP + API Plugin Middleware integration json output, served from nginx+phpfpm using php7.1 399.79 [#/sec] (mean) 250.133 [ms] (mean)
C) CakePHP + API Plugin Middleware integration json output, served from php-pm using php7.1 911.95 [#/sec] (mean) 109.656 [ms] (mean)
D) CakePHP + API Plugin Middleware integration json output, served from php-pm using php7.2 1811.66 [#/sec] (mean) 55.198 [ms] (mean)
  These results for a NOT OPTIMIZED CakePHP application are promising, and the improvement using PHP-PM is huge in this case. There are some important considerations though:
  • PHP-FPM is mature and stable, PHP-PM is still in early development, although there is a 1.0 version released already.
  • Processes need monitoring, specially regarding memory leaks, we would need to manage a restart policy and be able to hot-restart individual workers
  • System integration, init scripts are not provided, even if this is something easy to manage nowadays via systemd or monit, would be good to have for production
  • Application bootstrapping should not be affected by the request. If your application bootstrapping depends on the request params, or logged in user, you'll need to refactor your code
  • Session handling was not tested, issues are reported for PHP-PM for other frameworks. We were aiming to stateless API's so we don't know if this would be an issue for a regular application
Performance is always a concern for the API developer, applying proven paradigms like the event driven development (https://reactphp.org/) to your existing code would be the way to go and ensure backend frameworks like CakePHP will perform as required when dealing with the peaks we all love and hate. Edit: We've added a php7.2 based benchmark, with a huge performance improvement.

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 Bake with CakePHP