The events system has become an integral part of CakePHP since its introduction in 2.1. When originally introduced, we needed to make a number of compromises in the implementation to facilitate compatibility with the code it was replacing. For 3.0, I wanted to revisit the event subsystem and try to make things more efficient and streamline the API a bit.
A few months ago Christiaan Titos Bolivar and some fellow students did a fantastic analysis of the events system in CakePHP . Their conclusions proved my gut feeling that there was a good opportunity to optimize the events system for the next major release. Their work pointed out that the collection objects generated some overhead and complexity.
To make events simpler and more efficient my plan was to:
- Remove the workarounds/shims we put in place originally. These shims all need checking and the conditional logic increases complexity and costs runtime performance.
- Remove pointless indirection. This meant that Helper, Components, Behaviors would bind listeners onto their respective EventManager instead of being proxied by the collection object.
- Split the collection objects into their respective roles. These objects are presently half factory, half event dispatcher.
- Make events listeners consistent. Currently some events pass the event object while others do not.
- Remove no-op methods where possible. Profiling showed that measurable time was being spent binding and firing callbacks that did no actual work.
Splitting up the Collection classes
The collection objects were a welcome addition in 2.x. There was a natural split between their responsibilities around factory/registry features, as and callback handling. Since events were being streamlined, the collection objects could be replaced with a much simpler registry. Splitting the responsibilities allowed for significantly simpler code. It also meant that Tasks & TaskCollection wouldn’t have be forced to have features they don’t need.
The new registry objects provide the same loading API as in previous versions of CakePHP. However, they no longer offer the enable/disable features. This can now be done directly with the event manager:
- // To disable the Auth component's callbacks.
- // To enable callbacks on a component
Simplify event features
The events system previously provided a number of configuration options. Many of these were to support old features. 3.0 is going to be a large change for the framework as we remove many of the workarounds and cruft that have built up over time. The following features were removed from the event system to simplify it:* break * breakOn * modParams * collectReturn * omitSubject * passParams
All of these options were used to afford various edge-case features that have been streamlined into a single system. By default all events will pass the event and all the data as arguments. Events can only be stopped by using
return false from a listener. Various other options will be replaced by using object parameters.
While simplifying code can improve performance, the fastest code is the code that never runs. The base classes in CakePHP currently provide empty functions for each callback. In CakePHP 3.0, each callback will not be implemented by default. This allows CakePHP to use reflection and only bind the exact callback methods that are implemented. It also means that there is rarely a reason to call
parent anymore. All of these minor changes result in simpler code that is easier to use and faster to run.
With the various optimizations and streamlining done, my performance tests showed some nice improvements. I ran the following on my laptop, so the numbers should be better on a real server:
- Each set of tests was run with 5 components and 5 helpers enabled.
- The page rendered 1 element 10 times and did no database queries.
- I used
siege -c 5 -r 100 localhost/cake3/pages/rendertestto do the tests.
Before the changes, I was getting an average of 66.65 requests/second. After the changes I was getting 69.98 requests/second on average. This may not seem significant, but the improvements grow as your application uses more helpers and components. Each unimplemented callback method costs you nothing in CakePHP 3.0.