Thursday, December 19, 2013

Final day: some observations about extension building with Extension Builder and Extbase

We've arrived at the last week of the Hackership, and my goal this week was to master get pretty good at PHPUnit testing. To that end, I'm creating mock objects to test interactions in my "Organization" class with classes that have not yet been implemented. I ran into a tricky problem with this, in that the "magic" that Extbase is supposed to conjure in the following line:

 * Contacts of Organization
 * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\YouthAct\Domain\Model\Person>
protected $contacts;

doesn't actually work. Trying to use "contacts" later in the class leads to the fatal error "Call to member-function on a non-object". Thanks to a lot of help from one of the Hackership organizers, we found out that the "initStorageObjects" function, which instantiates the related objects, isn't complete:

protected function initStorageObjects() {
$this->contacts = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();

public function __construct() {
//if you don't add this following line of code, Extbase magic suffers an epic fail:

This kind of sucks. But at least now I don't get any more fatal errors: just errors about problems with my code. This is a good thing - this is how we learn! Using mock-objects allows me to test interactions of the class I'm testing with classes that haven't yet been implemented. This is a great alternative to creating a bunch of skeleton classes and wiring them up together. Additionally, using mock-objects to imitate the behavior of classes within an aggregate root makes for lean, strict and correct PHPUnit tests.

This week I built an Extension from mostly scratch, based on two domain-entities created with Extension Builder and implemented test-first with PHPUnit tests. Writing a hundred tests for getters and setters can lead to catatonia, so I decided to "see what would happen" if I were to supplement my domain-model with Extension Builder to include all domain-entities with their properties. My hope was that EB would create the new classes with getters and setters, and magically generate the test-files with the tedious setter and getter tests already done. Without breaking any of the code I'd already written. Hope springs eternal.

A note before I dive right in: no matter what your settings are in the "yaml.settings" file (i.e. "merge"), updating your domain model with the Extension Builder after having written your own code WILL OVERWRITE (almost) EVERYTHING. ALMOST everything. If it really overwrote everything, at least the code errors would be consistent! Luckily I made a hard backup of my files so that I could compare them to those generated by Extension Builder.

All of MY test code - gone. All of the setter and getter tests I had supplemented - overwritten. EB overwrites all the "getter" tests with yawning emptiness. Let me repeat: if you do not make a backup of your tests before expanding your domain-model with Extension Builder, EB WILL OVERWRITE YOUR HOPES AND DREAMS, leaving you in a yawning lacuna of existential emptiness. Hell is dynamically-generated code.

If that were not enough, here's an additional disadvantage: in its tests, EB takes care of class dependencies by instantiating objects instead of mocking them. This causes an interdependence between test classes that is NOT a good thing, and makes for test code that is difficult to correct when running it leads to failed tests. Additionally, EB has a strange habit of changing all "implements \TYPO3\whatever\this\interface" into "implements TYPO3\whatever\this\interface" EVERY time you save the domain-model through EB which, of course, leads to a fatal error which you will have to correct every time after pressing "save". Eventually I just commented out the "implements" and wrote it in after I was definitively done "saving" (ha!) the model with Extension Builder.

But wait, there's more! Everywhere a class needs to extend "Persistence\ObjectStorage", EB likes to overwrite that with "Persistence\Generic\ObjectStorage". This breaks all code, everywhere, and you will have to do a search-and-replace to get rid of the introduced errors.

"Extension Builder" might be more aptly named "Extension Wrecker". Take heed.

I ran the tests generated by EB again and again until everything passed. I think that that took as much time as writing them by hand, and the errors I had to correct were not actually introduced by me. THANKS (dripping with saracasm), Extension "Builder".

One good thing is that the otherwise nefarious Extension Builder actually added tests that helped me uncover the correct way of implementing, getting and setting storage objects. This was done in 2 different ways in the code I downloaded for "Reliable Extensions" and I'm glad that there were dynamically generated tests that could point out to me the flaw in logic in one of the implementations.

OK, so I think I'm done bitching for now. But I've come to the conclusion that, just like with the old "kickstarter" extension builder, you should really only use Extension Builder ONCE - or, never, ever again after you've started working with the code it generates. Seriously. I'm looking forward to see what the process is in my new place of employment: EB first, EB never, or EB always and forever?

It's been a very long, very productive, frusterating and also incredibly rewarding six weeks. Several times during my learning process I was able to move forward only with the help of fellow-students and organizers. At home, when I got stuck, I would always have to give up (Laravel, I'm looking at you -- YOU'RE NEXT).

It was especially in these past two weeks, where I could concentrate on domain-driven-design and test-driven-development with PHPUnit, that I've become a more professional object-oriented PHP coder than ever before. Within this short time I've gotten farther than in a whole TWO YEARS of working full-time with procedural PHP, taking online courses (in everything BUT PHP), and going to one to three programming meetups a week!

Thank you, Hackership, organizers and fellow-students, for giving me the opportunity of a lifetime - the opportunity to become a better programmer, to really learn, and practice, and share. Your support, your enthusiasm, has kept me going when things got frusterating, when it was too cold outside to want to get out of bed, when I came home after 9 hours of coding completely exhausted. You were motivation and reward.

I feel like I now know what it's like to work on a team that feels like a family.

No comments:

Post a Comment

Play nicely!