- On April 10, 2017
- 0 Comments
The days of CakePHP 3 plugins and how difficult it was to actually develop plugins are over.
Back in the days (OK, I still have to do it once in a while), there was even an app required to test a plugin. Since you didn’t want to have a boilerplate app for each plugin, you usually worked in your actual app.
so you had cross contamination from that messing up your tests and stuff. Really annoying.
The only thing I am still missing once in a while is the web test runner, as it speeds up things for some use cases (usually with browser related output).
While most of the concrete examples are about plugin development for CakePHP 3, the main ideas apply to all library code you write. And if you are a developer for other frameworks, the same principles apply, only the concrete implementation might differ. So you could skip the “real story” part.
Well, but now to the fun part I promised in the title.
CakePHP 3 plugin development – real story:
My goal was simple: Developing a simple plugin for hashids support in CakePHP in a few hours.
The CakePHP plugin docs mentioned a few basics, but in the following paragraphs I want
to go into more concrete details.
How do you start?
I started by creating a fresh GitHub repo “cakephp-hashid” and cloning it (git clone URL).
Then I added the boilerplate stuff like composer.json and gitignore file. You can either copy and paste from existing ones,or even bake your plugin code (cake bake plugin Name) and move it to your stand-alone plugin.
Keeping in the app is also possible, but I prefer to keep it outside and develop it test driven until it is in a working state.
This way you are usually faster. TDD – test driven development – actually helps to speed up development, and you get tests with it for free.
Now it was time to set up the behavior code and the constructor setup as well as a test file.
With php phpunit.phar I already got immediate results of the initial work, and could fix those with almost zero overhead.
As soon as I added more use cases, especially with some config options and edge cases, I quickly saw where things were not working as expected.But while getting those to run, I also saw if I broke the existing already working tests.
(Plugin) coding tips:
if you develop a plugin for the first time, take a look at the existing ones listed in the awesome-cakephp list.
They might give you some insight in how things can look like. How we add a bootstrap for testing, how a Travis file looks like etc.
Plugin vs. core feature:
This issue comes up every week basically. For me, beginning with CakePHP it was difficult to tell what should be covered by the core and what should stay as community plugin. I had this idea that every use case must be supported by the framework itself. Over time, it become more and more clear to me that a framework itself should stay lean and focus on the majority of the use cases and maybe provide a way to make it extensible for those edge case.
As a logical conclusion some of the CakePHP core functionality has been split off into it’s own repositories, like Acl, Migrations, Bake, Localized.
Not all of the users need those additional tools, in fact almost no one used Acl, and you only need Bake for development.
Try to follow coding and package principles:
With CakePHP 3 we can finally adhere more correctly to some very basic coding principles. Most of you might know (or at least heard) about SOLID and Package Principles.
They following tips go into more detail what it means for our CakePHP plugins.
Coding principles (SOLID):
1.Single responsibility principle (S):
It does not actually geocode, because that is the task of a specific class. To avoid it doing too much, the behavior only wraps this Geocoder class and forwards calls to it. This way the only responsibility of this behavior is to help the model layer (usually a Table class) to geocode the entity data, while the single responsibility of the Geocoder class is to perform this geocoding task by talking to an API.
The additional advantage is that we can also use the library class as standalone, so we might want to provide a GeocodeShell, for which we most certainly don’t want to use behavior to encode a simple input string.
2.Open/closed principle (O):
Your code should be open for extension, but closed for modification. You will most likely never be able to guess and support all use cases for your code out of the box. Often times people will have come up with new ways to use your plugin.
So at the one side you do not want to have to change your code for every possible scenario. But if it was possible to support a lot of extensions out of the box, why not doing this?
If we are using dependencies in our classes, we do not want to rely on a specific class dependency, but an interface.
3.Liskov substitution principle (L):
Every subclass or derived class should be substitutable for their base/parent class. So make sure you make don’t widen the input/constructors, but keep them the same or narrow them.
You can always become more strict, but not less. Interfaces also help with that, as they make sure that at least those common methods have been provided.
4.Interface segregation principle (I):
If you create one interface containing too many different method stubs, often times you limit the possibilities of implementation.
Often times those classes can be grouped by API or Non-API, and in either of those cases need only a subset of the interface methods.
In this case it will most likely make sense to have two specific interfaces for each use case, this allows the sub-parts of your code to only rely on those relevant methods they care about.
5.Dependency inversion principle (D):
Ideally, we always enforce class dependencies via constructor argument commonly known as “Constructor Dependency Injection”. When allowing to exchange the used class, we should respect that.
You never know if the class your plugin users want to use require some constructor dependencies on their own.
The DI principle should be possible for them to use, too, to fully respect the Open/Close Principle from above.
Framework Semantic Versioning:
With releasing plugins for a CakePHP version strict sever can be somewhat confusing, though (1.x/2.x here is for 3.x there, 3.x+ is for 4.x there, etc).
One more severe problem with that is that once you released a new 3.x framework compatible version you cannot major bump your 2.x code, as there is no number left in between.
Most of the time people are just afraid of major bumps and often use a minor one to introduce larger breaking changes.
In my book this makes it way more clear as the plugin itself cannot live without the CakePHP core dependency and at the same time has to be compliant to each of those different major versions.